UniCommon機能解説 - Crypt
Cryptモジュールには、暗号化などのセキュリティ関連のユーティリティが用意されています
SecureValue<T>
メモリ上での値改竄を防ぐ、セキュアな数値ラッパーです
AndroidなどのDME(Direct Memory Editor)を用いると、メモリ上の値を変えることが簡単にできます
これにより、UIに表示されている値などを頼りに、ゲームの状態を不正に書き換えることが可能なことがあります
本当にそうなのかはよく知らん。チートのプロならできそう。
それらのチートを防ぐために、メモリ上でセキュアな値を保持しない戦略をとるための機能です
SecureInt, SecureUInt, SecureFloat, SecureLongがあります
以下のように使います
code:cs
var coinCount = new SecureUInt(100);
Debugs.Log(coinCount.Value); // => 100
coinCount.Value -= 50;
Debugs.Log(cointCount.Value); // 50
理論は単純で、ラップした元の値をランダムなseedでget/setのたびにxor演算しているだけです
これだけでもメモリ上から値を探すのは不可能になります
Crypter
暗号化/復号化のためのクラスです
ソルトと秘密鍵を使ったAES暗号で任意データを暗号化します
以下のように使います
code:cs
// クリプタを作成
var crypter = Crypter.Default(() => "secret");
// 暗号化
var encrypted = crypter.TryEncrypt(new byte[] { 0, 1, 2});
// 復号化
var decrypted = crypter.TryDecrypt(encrypted); // bytes[] { 0, 1, 2}
Crypterを作成するためには、秘密鍵のプロバイダを渡す必要があります
それを使って暗号化します
現状唯一のCrypterのファクトリであるCrypter.Defaultは、引数に鍵文字列のプロバイダ関数をとります
暗号化に使う秘密鍵を返す関数であればなんでも良いです
この秘密鍵の提供方法はアプリのセキュリティに関わることなので、おまかせします
C#に直書き
Unityのリソースファイルに書く
サーバー経由で貰う
iOS/AndroidネイティブライブラリにC拡張関数を書いてDLLから読み出す
などなど…
暗号化と復号化は、同じ秘密鍵を使うクリプタでないとできません
なので、後述のVersionedと組み合わせて使うことをおすすめします
Versioned
どんなデータを保存するとしても、それがどのような形式で保存されたかを知る必要があることは多いと思います
jsonでも、中身のフォーマットのバージョンが何なのかを知りたいときなど
Versionedはバージョン文字列を埋め込んだ、任意形式のバイナリフォーマットです
データのレイアウトは以下のようになっています
code:Versioned.cs
private static readonly string Signature = "unicommonvsn";
private struct VersionedStructure {
public char[] signature;
public UInt32 versionLength;
public byte[] version; // UTF8形式文字列
public UInt32 dataLength;
public byte[] data; // データ本体
public int Size() {
return signature.Length + 4 + version.Length + 4 + data.Length;
}
}
Versioned.TryWrapメソッドを用いることで、特定のデータ構造を統一されたフォーマットでバージョン情報と一緒にバイナリ化できます
バージョンは数値でなくUTF8の可変長文字列なので、好きなバージョンを埋め込めます
例えば、データが作られたアプリのバージョンと、Content-Typeなどを混ぜることが出来ます
code:cs
var json = "{ \"hoge\" : 222 }";
var version = string.Fromat("{0}:application/json;utf-8",Application.version);
var bytes = Encoding.UTF8.GetBytes(json);
var wrapped = Versioned.TryWrap(version, bytes);
ここでwrappedは、次のようなデータになります
code:bin
unicommonvsn // signature
29 // version長
1.0.0:application/json;utf-8 // バージョン情報
16 // data長
{ "hoge" : 222 } // データ本体
バージョン化されたデータは、Versioned.TryUnWrapで読み出せます
code:cs
var unwrapped = Versioned.TryUnWrap(bytes);
Debugs.Log(unwrapped.Version); // "1.0.0:application/json;utf-8"
Debugs.Log(Encoding.UTF8.GetString(unwrapped.Data)); // { "hoge": 222 }
バージョン情報を読み出したあと、任意の方法でデータ本体を復元してください
CrypterとVersionedを組み合わせる
CrypterとVersionedを組み合わせると、より堅牢で柔軟な永続化データを作成できます
code:cs
var crypter = Crypter.Default(() => "secret");
var json = "{ \"hoge\" : 222 }";
var version = string.Fromat("{0}:application/encrypted-json;utf-8",Application.version);
var crypted = crypter.TryEncrypt(Encoding.UTF8.GetBytes(json));
var wrapped = Versioned.TryWrap(version, crypted);
ここでwrappedはバージョン情報がついていて、dataは暗号化されたデータになります
バージョン情報を暗号化しないことで、復号化の時に柔軟にDecrypterを選択できるというメリットがあります